#include "QcDictItemModel.h"
#include <QDir>
#include <QApplication>
#include <QDirIterator>
#include <QFileIconProvider>
#include "QuFile.h"

QcDictItemModel::QcDictItemModel(QObject *parent)
    : QAbstractItemModel(parent)
{
    root_ = new KpDictItem(nullptr);
    QDir dir(QApplication::applicationDirPath());
    dir.mkdir(modelDir_());
    root_->name = dir.absolutePath() + "/" + modelDir_();
}

QcDictItemModel::~QcDictItemModel()
{
    deleteItem_(root_);
}

QVariant QcDictItemModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (orientation != Qt::Horizontal || role != Qt::DisplayRole || section >= columnCount())
        return QVariant();

    switch(section) {
    case K_WORD: return "英文";
    case K_PRON: return "发音";
    case K_POS:  return "词性";
    default:     return "中文";
    }
}

QModelIndex QcDictItemModel::index(int row, int column, const QModelIndex &parent) const
{
    return hasIndex(row, column, parent)
        ? createIndex(row, column, std::any_cast<KpDictItem*>(item_(parent)->children[row]))
        : QModelIndex();
}

QModelIndex QcDictItemModel::parent(const QModelIndex &index) const
{
    Q_ASSERT(index.isValid());

    auto pitem = item_(index)->parent;
    Q_ASSERT(pitem && !pitem->isWord);

    auto ppitem = pitem->parent;
    if (!ppitem) return QModelIndex();

    Q_ASSERT(!ppitem->isWord);

    // 获取父节点pitem的行号
    int row = 0;
    for (auto i = ppitem->children.cbegin(); i != ppitem->children.cend(); i++, row++)
        if (std::any_cast<KpDictItem*>(*i) == pitem)
            break;
    return createIndex(row, 0, pitem);
}

int QcDictItemModel::rowCount(const QModelIndex &parent) const
{
    auto item = item_(parent);
    return item->isWord ? 0 : int(item->children.size());
}

int QcDictItemModel::columnCount(const QModelIndex &parent) const
{
    return K_COLUMN_COUNT;
}

bool QcDictItemModel::hasChildren(const QModelIndex &parent) const
{
    auto item = item_(parent);
    return !item->isWord; // && !(item->fetched && item->children.empty());
}

bool QcDictItemModel::canFetchMore(const QModelIndex &parent) const
{
    auto item = item_(parent);
    return !item->fetched && !item->isWord;
}

void QcDictItemModel::fetchMore(const QModelIndex &parent)
{
    Q_ASSERT(canFetchMore(parent));

    auto item = item_(parent);

    // 加载目录
    QDirIterator iter(fullPath_(item), QDir::NoDotAndDotDot | QDir::Dirs);
    while(iter.hasNext()) {
        iter.next();
        auto child = new KpDictItem(item);
        child->name = iter.fileName();
        item->children.push_back(child);
    }

    // 加载词典文件
    loadWords_(item);

    item->fetched = true;
}

QVariant QcDictItemModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    if (role == Qt::DisplayRole || role == Qt::EditRole) {
        auto item = item_(index);
        if (!item->isWord)
            return item->name;

        if (role == Qt::EditRole) {
            // 构造词条的EditRole
            QStringList sl;
            sl << item->name;
            for (auto& i : item->children)
                sl << std::any_cast<QString>(i);

            while(sl.size() < K_COLUMN_COUNT)
                sl << "";

            return sl;
        }
        else {
            if (index.column() == 0)
                return item->name;
            else if (index.column() <= item->children.size())
                return std::any_cast<QString>(item->children[index.column() - 1]);
        }
    }
    else if (role == Qt::DecorationRole) {
        auto item = item_(index);
        auto iconType = item->isWord ? QFileIconProvider::File : QFileIconProvider::Folder;
        return QFileIconProvider().icon(iconType);
    }


    return QVariant();
}

bool QcDictItemModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (role == Qt::EditRole && data(index, role) != value) {
        auto item = item_(index);
        if (item->isWord || value.metaType().id() == QMetaType::QStringList) {
            auto sl = value.toStringList();
            Q_ASSERT(sl.size() == K_COLUMN_COUNT);
            item->name = sl.front(); sl.pop_front();
            item->children.resize(sl.size());
            for (int i = 0; i < sl.size(); i++)
                item->children[i] = sl[i];

            item->isWord = true;
            return saveWords_(item->parent);
        }
        else { // 修改目录名
            QDir dir(fullPath_(item->parent));
            if (item->name.isEmpty()) {
                if (!dir.mkdir(value.toString()))
                    return false;
            }
            else if (!dir.rename(item->name, value.toString())) {
                return false;
            }

            item->name = value.toString();
        }

        emit dataChanged(index, index, {role});
        return true;
    }
    return false;
}

Qt::ItemFlags QcDictItemModel::flags(const QModelIndex &index) const
{
    if (!this->checkIndex(index)) // NB: VERY important!!!
        return Qt::ItemFlags{};

    auto flag = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
    flag |= item_(index)->isWord ? Qt::ItemNeverHasChildren : Qt::ItemIsEditable | Qt::ItemIsUserCheckable;

    return flag;
}

bool QcDictItemModel::insertRows(int row, int count, const QModelIndex &parent)
{
    this->beginInsertRows(parent, row, row + count - 1);

    // 此时不知道插入的是词条还是目录，先默认为目录，后续根据setData进行调整
    auto item = item_(parent);
    for (int i = 0; i < count; i++)
        item->children.insert(item->children.begin() + row + i, new KpDictItem(item));

    this->endInsertRows();

    return true;
}

bool QcDictItemModel::removeRows(int row, int count, const QModelIndex &parent)
{
    this->beginRemoveRows(parent, row, row + count - 1);

    auto item = item_(parent);
    for (int i = 0; i < count; i++) {
        auto child = std::any_cast<KpDictItem*>(item->children[row]);
        item->children.erase(item->children.begin() + row);

        bool done(true);
        if (child->isWord) {
            done = saveWords_(item); // 删除词条，更新词典文件
        }
        else {
            QDir dir(fullPath_(child));
            done = dir.removeRecursively();
        }

        if (!done) {
            item->children.insert(item->children.begin() + row, child); // 回退，重新把child插回去
            this->endRemoveRows();
            return false;
        }

        deleteItem_(child);
    }

    this->endRemoveRows();

    return true;
}

QString QcDictItemModel::dictRootPath() const
{
    return root_->name;
}

QString QcDictItemModel::modelDir_() const
{
    return "dict";
}

QString QcDictItemModel::dictFileName_() const
{
    return "words.txt";
}

QcDictItemModel::KpDictItem* QcDictItemModel::item_(const QModelIndex &index) const
{
    return !index.isValid() ? root_ : static_cast<KpDictItem*>(index.internalPointer());
}

QString QcDictItemModel::fullPath_(KpDictItem* parent) const
{
    if (parent == root_)
        return root_->name;

    return fullPath_(parent->parent) + "/" + parent->name;
}

QString QcDictItemModel::dictPath_(KpDictItem* parent) const
{
    return fullPath_(parent) + "/" + dictFileName_();
}

void QcDictItemModel::loadWords_(KpDictItem* parent)
{
    auto texMat = QuFile::readTextMatrix<5>(dictPath_(parent));
    for (auto& row : texMat) {
        auto child = new KpDictItem(parent);
        child->isWord = true;
        child->name = row[0];
        for (int i = 1; i < 5; i++)
            child->children.push_back(row[i].replace("\\n", "\n").replace("\\t", "\t"));

        parent->children.push_back(child);
    }
}

bool QcDictItemModel::saveWords_(KpDictItem* parent)
{
    QFile f(dictPath_(parent));
    if (!f.open(QFile::Truncate | QFile::WriteOnly | QFile::Text))
        return false;

    for (auto& i : parent->children) {
        auto item = std::any_cast<KpDictItem*>(i);
        if (!item->isWord) continue;
        QuFile::writeLine(f, formatWordLine_(item));
    }

    return true;
}

QString QcDictItemModel::formatWordLine_(KpDictItem* item)
{
    Q_ASSERT(item->children.size() == 4);

    QString str = item->name;
    str.replace("\t", "\\t").replace("\n", "\\n");
    for (auto& i : item->children) {
        QString s = std::any_cast<QString>(i);
        s.replace("\t", "\\t").replace("\n", "\\n");
        str += "\t" + s;
    }

    return str;
}

void QcDictItemModel::deleteItem_(KpDictItem* p)
{
    if (!p->isWord) {
        for (auto& i : p->children)
            deleteItem_(std::any_cast<KpDictItem*>(i));
    }

    p->children.clear();
    delete p;
}

QModelIndex QcDictItemModel::indexFromPath(const QString& path) const
{
    QDir dir(root_->name);
    auto rpath = dir.relativeFilePath(path);
    auto names = rpath.split('/', Qt::SkipEmptyParts);

    QModelIndex index;
    for (const auto& i : names) {
        index = indexFromName(i, index);
        if (!index.isValid())
            break;
    }

    return index;
}

QModelIndex QcDictItemModel::indexFromName(const QString& name, const QModelIndex& parent) const
{
    auto item = item_(parent);
    Q_ASSERT(!item->isWord);

    for (int i = 0; i < item->children.size(); i++) {
        auto child = std::any_cast<KpDictItem*>(item->children[i]);
        if (child->name == name)
            return index(i, 0, parent);
    }

    return QModelIndex(); // 未找到，返回invalid-index
}
